package pl.enigmatic.math.perm;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import pl.enigmatic.math.BijectionSequence;


/**
 * Represents a permutation on a given symbols set.
 * @author reggie_7
 * @param <S> symbols to use' type
 */
public class SymbolsPermutation<S> extends Permutation {

	private static final long serialVersionUID = 1L;

	/** a continous sequence of symbols */
	private final BijectionSequence<S> symbols;

	/**
	 * Constructs a permutation on the given <code>symbols</code> sequence
	 * @param symbols the symbols sequence to use
	 */
	protected SymbolsPermutation(final BijectionSequence<S> symbols) {
		super(symbols.size());
		this.symbols = symbols;
	}

	/**
	 * Constructs a permutation on the given <code>symbols</code> sequence
	 * @param symbols the symbols sequence to use
	 */
	public SymbolsPermutation(final S... symbols) {
		this(new BijectionSequence<S>(symbols));
	}

	/**
	 * Returns <code>true</code> if <code>this</code> permutation acts on <code>s</code> symbol.
	 * @param s the symbol whose presence in <code>this</code> permutation is to be tested
	 * @return <code>true</code> if <code>this</code> permutation acts on <code>s</code>
	 */
	public boolean actsOn(final S s) {
		return symbols.containsY(s);
	}

	/**
	 * Returns a {@link Set} view of the symbol mappings <code>this</code> permutation acts on. This call should be used only to allow iteration.
	 * Changing the result set itself may cause inconsistency of data.
	 * @return a set view of the <code>S</code> &rarr; <code>Integer</code> symbol mappings in <code>this</code> permutation
	 */
	public Set<Entry<Integer, S>> symbolsEntrySet() {
		return symbols.entrySetX();
	}

	// TODO javadoc
	public int getInteger(final S s) {
		return symbols.getX(s);
	}

	public S getSymbol(final int x) {
		return symbols.getY(x);
	}

	public Set<S> symbolsSet() {
		return symbols.setY();
	}

	public S at(final S position) {
		return symbols.getY(at(symbols.getX(position)));
	}

	public S symbolAt(final int position) {
		return symbols.getY(at(position));
	}

	public S invAt(final S position) {
		return symbols.getY(invAt(symbols.getX(position)));
	}

	public Set<S> getSymbols(final Set<Integer> set) {
		return symbols.convertX(set);
	}

	public Set<Integer> getIntegers(final Set<S> set) {
		return symbols.convertY(set);
	}

	public List<S> encipher(final S... plaintext) {
		final List<S> res = new ArrayList<S>();
		for (final S element : plaintext) {
			res.add(at(element));
		}
		return res;
	}

	public boolean set(final S[][] cycles) {
		final int[][] ints = new int[cycles.length][];
		for (int i = 0; i < cycles.length; i++) {
			ints[i] = symbols.getIndexes(cycles[i]);
		}
		return set(ints);
	}

	public boolean isFixedAt(final S position) {
		return isFixedAt(symbols.getX(position));
	}

	public boolean removeCycle(final S position) {
		return removeCycle(symbols.getX(position));
	}

	public boolean transposes(final S i, final S j) {
		return transposes(symbols.getX(i), symbols.getX(j));
	}

	public void transpose(final S i, final S j) {
		transpose(symbols.getX(i), symbols.getX(j));
	}

	public int powerAt(final int m, final S position) {
		return powerAt(m, symbols.getX(position));
	}

	public int[] getCycle(final S x) {
		return getCycle(symbols.getX(x));
	}

	public boolean set(final S[] array) {
		return super.set(symbols.getIndexes(array));
	}

	/**
	 * Tries to clone <code>this</code> object instance.
	 * @see java.lang.Object#clone()
	 */
	@Override
	public SymbolsPermutation<S> clone() {
		SymbolsPermutation<S> res = new SymbolsPermutation<S>(symbols);
		res.set(array());
		return res;
	}

	@Override
	public String toString() {
		String result = "";
		for (int i = 0; i < length(); i++) {
			result += symbols.getY(at(i));
		}
		return getClass().getSimpleName() + ": " + result;
	}

	public String toStringHS() {
		if (length() == 0) { return "[]"; }
		String res = "";
		for (int i = 0; i < length(); i++) {
			res += symbols.getY(at(i)) + ", ";
		}
		return "[" + res.substring(0, res.length() - 2) + "]";
	}

	public String toStringVS(final String starterPlus) {
		String result = "";
		for (int i = 0; i < length() - 1; i++) {
			result += starterPlus + symbols.getY(i) + "->" + symbols.getY(at(i)) + '\n';
		}
		result += starterPlus + symbols.getY(length() - 1) + "->" + symbols.getY(at(length() - 1));
		return result;
	}

	public String toStringVS() {
		return toStringVS("");
	}

	public String toStringCS() {
		final int[][] cycles = getAllCycles();
		if (cycles.length == 0) { return "()"; }
		String result = "";
		for (final int[] cycle : cycles) {
			result += '(';
			for (int j = 0; j < cycle.length; j++) {
				result += symbols.getY(cycle[j]);
			}
			result += ')';
		}
		return result;
	}

}
